/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package prototype.runtime.federate;

import hla.rti.FederatesCurrentlyJoined;
import hla.rti.FederationExecutionAlreadyExists;
import hla.rti.FederationExecutionDoesNotExist;
import hla.rti.LogicalTime;
import hla.rti.LogicalTimeInterval;
import hla.rti.RTIambassador;
import hla.rti.RTIexception;
import hla.rti.ResignAction;
import hla.rti.SuppliedParameters;
import hla.rti.jlc.RtiFactoryFactory;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import prototype.persistence.pojo.Position;
import prototype.persistence.pojo.Scenario;
import prototype.persistence.pojo.User;
import prototype.runtime.federate.interactions.Death;
import prototype.runtime.federate.interactions.Fire;
import prototype.runtime.interfaces.Order;
import prototype.runtime.interfaces.RuntimeGoal;
import prototype.runtime.interfaces.RuntimeUnit;
import prototype.runtime.porticoutils.PorticoUtils;

/**
 *
 * @author Server
 */
public class RuntimeUser implements Runnable {

    private List<RuntimeGoal> runtimeGoals = new ArrayList<RuntimeGoal>();
    private List<RuntimeUnit> runtimeUnits = new ArrayList<RuntimeUnit>();
    private RTIambassador rtiAmbassador;
    private RuntimeUserFederateAmbassador fedAmb;
    private User user;
    private Scenario scenario;
    public static final String READY_TO_RUN = "ReadyToRun";
    private boolean simulationContinues;
    private String federationName;
    private boolean AIControlled;
    private int fireInteractionHandle;
    private int deathInteractionHandle;
    protected Vector<Death> deaths = new Vector<Death>();
    private Vector<Position> fireLocations = new Vector<Position>();

    public RuntimeUser(User user, Scenario scenario) {
        this.user = user;
        this.fedAmb = new RuntimeUserFederateAmbassador(this);
        this.scenario = scenario;
        this.AIControlled = false;
        this.federationName = "Prototype";
        this.simulationContinues = true;
    }

    public void run() {
        try {
            runFederate();
        } catch (RTIexception ex) {
            Logger.getLogger(RuntimeUser.class.getName() + federationName).log(Level.SEVERE, null, ex);
        }
    }

    private void log(String message) {
        System.out.println(user.getName() + ": " + message);
    }

    private void runFederate() throws RTIexception {
        if (isAIControlled() || user.getName().equals("Civilians")) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException ex) {
                Logger.getLogger(RuntimeUser.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        rtiAmbassador = (RTIambassador) RtiFactoryFactory.getRtiFactory().createRtiAmbassador();

        try {
            File fom = new File("src/warfaresimulator.fed");

            rtiAmbassador.createFederationExecution(federationName, fom.toURI().toURL());
            log("Created Federation");
        } catch (FederationExecutionAlreadyExists exists) {
            log("Didn't create federation, it already existed");
        } catch (MalformedURLException urle) {
            log("Exception processing fom: " + urle.getMessage());
            urle.printStackTrace();

            return;
        }

        // //////////////////////////
        // 3. join the federation //
        // //////////////////////////
        // create the federate ambassador and join the federation
        rtiAmbassador.joinFederationExecution(user.getName(), federationName, fedAmb);
        log("Joined Federation as " + user.getName());

        // //////////////////////////////
        // 4. announce the sync point //
        // //////////////////////////////
        // announce a sync point to get everyone on the same page. if the point
        // has already been registered, we'll get a callback saying it failed,
        // but we don't care about that, as long as someone registered it

        rtiAmbassador.registerFederationSynchronizationPoint(READY_TO_RUN, null);

        // wait until the point is announced
        while (fedAmb.isAnnounced() == false) {
            rtiAmbassador.tick();
        }

        if (!isAIControlled() && !user.getName().equals("Civilians")) {
            try {
                System.in.read();
            } catch (IOException ex) {
                Logger.getLogger(RuntimeUser.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            Thread.yield();
        }
        // WAIT FOR USER TO KICK US OFF
        // So that there is time to add other federates, we will wait until the
        // user hits enter before proceeding. That was, you have time to start
        // other federates.
        ///////////////////////////////////////////////////////
        // 5. achieve the point and wait for synchronization //
        ///////////////////////////////////////////////////////
        // tell the RTI we are ready to move past the sync point and then wait
        // until the federation has synchronized on
        rtiAmbassador.synchronizationPointAchieved(READY_TO_RUN);
        log("Achieved sync point: " + READY_TO_RUN + ", waiting for federation...");
        while (fedAmb.isReadyToRun() == false) {
            rtiAmbassador.tick();
        }

        enableTimePolicy();
        log("Time Policy Enabled");

        /////////////////////////////////////////////////////
        // publish the interaction class InteractionRoot.X //
        /////////////////////////////////////////////////////
        // we want to send interactions of type InteractionRoot.X, so we need
        // to tell the RTI that we're publishing it first. We don't need to
        // inform it of the parameters, only the class, making it much simpler
        fireInteractionHandle = rtiAmbassador.getInteractionClassHandle("InteractionRoot.Fire");
        deathInteractionHandle = rtiAmbassador.getInteractionClassHandle("InteractionRoot.Death");

        // do the publication
        rtiAmbassador.publishInteractionClass(fireInteractionHandle);
        rtiAmbassador.publishInteractionClass(deathInteractionHandle);

        ////////////////////////////////////////////////////
        // subscribe to the InteractionRoot.X interaction //
        ////////////////////////////////////////////////////
        // we also want to receive other interaction of the same type that are
        // sent out by other federates, so we have to subscribe to it first
        rtiAmbassador.subscribeInteractionClass(fireInteractionHandle);
        rtiAmbassador.subscribeInteractionClass(deathInteractionHandle);

        while (simulationContinues) {
            /*synchronized (this) {
                fireLocations.removeAllElements();
            }*/
            for (RuntimeUnit ru : runtimeUnits) {
                if (ru.getUnit().getUnit().getHealth() < 0) {
                    continue;
                }
                Order o = null;
                if (ru.isAI()) {
                    o = ru.getAIOrder(scenario);
                } else if (!ru.hasOrder()) {
                    continue;
                } else {
                    o = ru.executeOrder(null);
                }

                if (o != null) {
                   /*synchronized (this) {
                        fireLocations.add(0, ru.getUnit().getPosition());
                        fireLocations.add(0, o.getPosition());
                        fireLocations.setSize(2);
                    }*/
                    sendInteraction(generateFireInteraction(o, ru));
                    log("Fire interaction sent.");
                }
            }
            while (!deaths.isEmpty()) {
                Death d = deaths.get(0);
                sendDeathInteraction(d);
                deaths.remove(0);
            }
            for (RuntimeGoal g : runtimeGoals) {
                g.check(scenario, this);
            }
            advanceTime(1.0);
            log("Time Advanced to " + fedAmb.getFederateTime());
        }
        rtiAmbassador.resignFederationExecution(ResignAction.NO_ACTION);
        log("Resigned from Federation");

        ////////////////////////////////////////
        // 12. try and destroy the federation //
        ////////////////////////////////////////
        // NOTE: we won't die if we can't do this because other federates
        //       remain. in that case we'll leave it for them to clean up
        try {
            rtiAmbassador.destroyFederationExecution("ExampleFederation");
            log("Destroyed Federation");
        } catch (FederationExecutionDoesNotExist dne) {
            log("No need to destroy federation, it doesn't exist");
        } catch (FederatesCurrentlyJoined fcj) {
            log("Didn't destroy federation, federates still joined");
        }
        System.exit(0);
    }

    /**
     * This method will attempt to enable the various time related properties for
     * the federate
     */
    private void enableTimePolicy() throws RTIexception {
        LogicalTime currentTime = PorticoUtils.convertTime(fedAmb.getFederateTime());
        LogicalTimeInterval lookahead = PorticoUtils.convertInterval(fedAmb.getFederateLookahead());

        ////////////////////////////
        // enable time regulation //
        ////////////////////////////
        this.rtiAmbassador.enableTimeRegulation(currentTime, lookahead);

        // tick until we get the callback
        while (fedAmb.isRegulating() == false) {
            rtiAmbassador.tick();
        }

        /////////////////////////////
        // enable time constrained //
        /////////////////////////////
        this.rtiAmbassador.enableTimeConstrained();

        // tick until we get the callback
        while (fedAmb.isConstrained() == false) {
            rtiAmbassador.tick();
        }
    }

    /**
     * This method will request a time advance to the current time, plus the given
     * timestep. It will then wait until a notification of the time advance grant
     * has been received.
     */
    private void advanceTime(double timestep) throws RTIexception {
        try {
            // request the advance
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(RuntimeUser.class.getName()).log(Level.SEVERE, null, ex);
        }
        fedAmb.setAdvancing(true);
        LogicalTime newTime = PorticoUtils.convertTime(fedAmb.getFederateTime()
                + timestep);
        rtiAmbassador.timeAdvanceRequest(newTime);

        // wait for the time advance to be granted. ticking will tell the
        // LRC to start delivering callbacks to the federate
        while (fedAmb.isAdvancing()) {
            rtiAmbassador.tick();
        }
    }

    private void sendInteraction(Fire f) throws RTIexception {

        int interactionHandle = rtiAmbassador.getInteractionClassHandle("InteractionRoot.Fire");
        if (interactionHandle != fireInteractionHandle) {
            System.out.println("lahooovv");
        }
        int unitIdH = rtiAmbassador.getParameterHandle("unitId", interactionHandle);
        int posXH = rtiAmbassador.getParameterHandle("positionX", interactionHandle);
        int posYH = rtiAmbassador.getParameterHandle("positionY", interactionHandle);
        int posZH = rtiAmbassador.getParameterHandle("positionZ", interactionHandle);
        int powerH = rtiAmbassador.getParameterHandle("power", interactionHandle);
        int radiusH = rtiAmbassador.getParameterHandle("radius", interactionHandle);

        SuppliedParameters parameters =
                f.getParameters(unitIdH, posXH, posYH, posZH, powerH, radiusH);

        log("Fire interaction sent to:" + f.getPosition().getX() + ","
                + f.getPosition().getY() + ","
                + f.getPosition().getZ() + ",");
        rtiAmbassador.sendInteraction(interactionHandle, parameters, generateTag());
    }

    private byte[] generateTag() {
        return ("" + System.currentTimeMillis()).getBytes();
    }

    private Fire generateFireInteraction(Order order, RuntimeUnit runtimeUnit) {
        Fire f = new Fire();
        f.setUnitId(runtimeUnit.getUnit().getId());
        f.setPosition(order.getPosition());
        f.setPower(runtimeUnit.getUnit().getUnit().getFirePower());
        f.setRadius(runtimeUnit.getUnit().getUnit().getFireEffectRadius());
        return f;
    }

    void sendDeathInteraction(Death d) throws RTIexception {
        //int interactionHandle = rtiAmbassador.getInteractionClassHandle("InteractionRoot.Death");
        int idH = rtiAmbassador.getParameterHandle("id", deathInteractionHandle);
        rtiAmbassador.sendInteraction(deathInteractionHandle, d.getParameters(idH), generateTag());
        log("Death interaction sent with id: " + d.getId());

    }


    /*
     *  Getters and Setters
     */
    public List<RuntimeGoal> getRuntimeGoals() {
        return runtimeGoals;
    }

    public List<RuntimeUnit> getRuntimeUnits() {
        return runtimeUnits;
    }

    public void setRuntimeGoals(List<RuntimeGoal> runtimeGoals) {
        this.runtimeGoals = runtimeGoals;
    }

    public void setRuntimeUnits(List<RuntimeUnit> runtimeUnits) {
        this.runtimeUnits = runtimeUnits;
    }

    public User getUser() {
        return user;
    }

    public void setFederationName(String federationName) {
        this.federationName = federationName;
    }

    public void setAIControlled(boolean AIControlled) {
        this.AIControlled = AIControlled;
        for (RuntimeUnit ru : runtimeUnits) {
            ru.assignToAI();
        }
    }

    public void setScenario(Scenario scenario) {
        this.scenario = scenario;
    }

    public Scenario getScenario() {
        return scenario;
    }

    public boolean isAIControlled() {
        return AIControlled;
    }

    public int getDeathInteractionHandle() {
        return deathInteractionHandle;
    }

    public int getFireInteractionHandle() {
        return fireInteractionHandle;
    }

    public Vector<Position> getFireLocations() {
        synchronized (this) {
            return (Vector<Position>) fireLocations.clone();
        }
    }

    public void stopSimulation() {
        simulationContinues = false;
    }
}
